listview: Change anchor handling again
authorBenjamin Otte <otte@redhat.com>
Mon, 24 Sep 2018 22:16:27 +0000 (00:16 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Sat, 30 May 2020 23:26:45 +0000 (19:26 -0400)
The anchor is now a tuple of { listitem, align }.

Using the actual list item allows keeping the anchor across changes
in position (ie when lists get resorted) while still being able to fall
back to positions (list items store their position) when an item gets
removed.

The align value is in the range [0..1] and defines where in the visible
area to do the alignment.
0.0 means to align the top of the row with the top of the visible area,
1.0 aligns the bottom of the widget with the visible area and 0.5 keeps
the center of the widget at the center of the visible area.
It works conceptually the same as percentages in CSS background-position
(where the background area and the background image's size are matched
the same way) or CSS transform-origin.

gtk/gtklistitemmanager.c
gtk/gtklistitemmanagerprivate.h
gtk/gtklistview.c

index d365137d1816a9b19659067f4ff17bdae618d66e..cce8eda34d95f2e147277b9c1a36d7ac8e55a288 100644 (file)
@@ -204,6 +204,30 @@ gtk_list_item_manager_end_change (GtkListItemManager       *self,
   g_slice_free (GtkListItemManagerChange, change);
 }
 
+/*
+ * gtk_list_item_manager_change_contains:
+ * @change: a #GtkListItemManagerChange
+ * @list_item: The item that may have been released into this change set
+ *
+ * Checks if @list_item has been released as part of @change but not been
+ * reacquired yet.
+ *
+ * This is useful to test before calling gtk_list_item_manager_end_change()
+ * if special actions need to be performed when important list items - like
+ * the focused item - are about to be deleted.
+ *
+ * Returns: %TRUE if the item is part of this change
+ **/
+gboolean
+gtk_list_item_manager_change_contains (GtkListItemManagerChange *change,
+                                       GtkWidget                *list_item)
+{
+  g_return_val_if_fail (change != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE);
+
+  return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == list_item;
+}
+
 /*
  * gtk_list_item_manager_acquire_list_item:
  * @self: a #GtkListItemManager
index d1cdfaf8194987decc1f196b6fd752b48e5c18a1..cf7721bc6ce9a8e43a0d287080f78e117510f140 100644 (file)
@@ -55,6 +55,9 @@ GtkListItemManagerChange *
                         gtk_list_item_manager_begin_change      (GtkListItemManager     *self);
 void                    gtk_list_item_manager_end_change        (GtkListItemManager     *self,
                                                                  GtkListItemManagerChange *change);
+gboolean                gtk_list_item_manager_change_contains   (GtkListItemManagerChange *change,
+                                                                 GtkWidget              *list_item);
+
 GtkWidget *             gtk_list_item_manager_acquire_list_item (GtkListItemManager     *self,
                                                                  guint                   position,
                                                                  GtkWidget              *next_sibling);
index 36e710ab18d6cd46db5342175564496ff74e39bf..35866d47b7e7e8310eef0e79f0b80ee898c6815f 100644 (file)
@@ -54,10 +54,8 @@ struct _GtkListView
   int list_width;
 
   /* managing the visible region */
-  guint anchor_pos;
-  int anchor_dy;
-  guint first_visible_pos;
-  guint lasst_visible_pos;
+  GtkWidget *anchor;
+  int anchor_align;
 };
 
 struct _ListRow
@@ -284,6 +282,54 @@ gtk_list_view_get_list_height (GtkListView *self)
   return aug->height;
 }
 
+static void
+gtk_list_view_set_anchor (GtkListView *self,
+                          guint        position,
+                          double       align)
+{
+  ListRow *row;
+
+  g_assert (align >= 0.0 && align <= 1.0);
+
+  row = gtk_list_view_get_row (self, position, NULL);
+  if (row == NULL)
+    {
+      /* like, if the list is empty */
+      self->anchor = NULL;
+      self->anchor_align = 0.0;
+      return;
+    }
+
+  self->anchor = row->widget;
+  self->anchor_align = align;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
+                                           GtkListView   *self)
+{
+  if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL])
+    {
+      ListRow *row;
+      guint pos;
+      int dy;
+
+      row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy);
+      if (row)
+        pos = list_row_get_position (self, row);
+      else
+        pos = 0;
+
+      gtk_list_view_set_anchor (self, pos, 0);
+    }
+  else
+    { 
+      gtk_widget_queue_allocate (GTK_WIDGET (self));
+    }
+}
+
 static void
 gtk_list_view_update_adjustments (GtkListView    *self,
                                   GtkOrientation  orientation)
@@ -306,15 +352,21 @@ gtk_list_view_update_adjustments (GtkListView    *self,
       page_size = gtk_widget_get_height (GTK_WIDGET (self));
       upper = gtk_list_view_get_list_height (self);
 
-      row = gtk_list_view_get_row (self, self->anchor_pos, NULL);
+      if (self->anchor)
+        row = gtk_list_view_get_row (self, gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)), NULL);
+      else
+        row = NULL;
       if (row)
         value = list_row_get_y (self, row);
       else
         value = 0;
-      value += self->anchor_dy;
+      value -= self->anchor_align * (page_size - (row ? row->height : 0));
     }
   upper = MAX (upper, page_size);
 
+  g_signal_handlers_block_by_func (self->adjustment[orientation],
+                                   gtk_list_view_adjustment_value_changed_cb,
+                                   self);
   gtk_adjustment_configure (self->adjustment[orientation],
                             value,
                             0,
@@ -322,6 +374,9 @@ gtk_list_view_update_adjustments (GtkListView    *self,
                             page_size * 0.1,
                             page_size * 0.9,
                             page_size);
+  g_signal_handlers_unblock_by_func (self->adjustment[orientation],
+                                     gtk_list_view_adjustment_value_changed_cb,
+                                     self);
 }
 
 static void
@@ -413,7 +468,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
 
   /* step 4: actually allocate the widgets */
   child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]);
-  child_allocation.y = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]);
+  child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]));
   child_allocation.width = self->list_width;
   for (row = gtk_rb_tree_get_first (self->rows);
        row != NULL;
@@ -425,6 +480,23 @@ gtk_list_view_size_allocate (GtkWidget *widget,
     }
 }
 
+static guint
+gtk_list_view_get_anchor (GtkListView *self,
+                          double      *align)
+{
+  guint anchor_pos;
+
+  if (self->anchor)
+    anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+  else
+    anchor_pos = 0;
+
+  if (align)
+    *align = self->anchor_align;
+
+  return anchor_pos;
+}
+
 static void
 gtk_list_view_remove_rows (GtkListView              *self,
                            GtkListItemManagerChange *change,
@@ -432,24 +504,11 @@ gtk_list_view_remove_rows (GtkListView              *self,
                            guint                     n_rows)
 {
   ListRow *row;
-  guint i, n_remaining;
+  guint i;
 
   if (n_rows == 0)
     return;
 
-  n_remaining = self->model ? g_list_model_get_n_items (self->model) : 0;
-  if (self->anchor_pos >= position + n_rows)
-    {
-      self->anchor_pos -= n_rows;
-    }
-  else if (self->anchor_pos >= position)
-    {
-      self->anchor_pos = position;
-      if (self->anchor_pos > 0 && self->anchor_pos >= n_remaining)
-        self->anchor_pos = n_remaining - 1;
-      self->anchor_dy = 0;
-    }
-
   row = gtk_list_view_get_row (self, position, NULL);
 
   for (i = 0; i < n_rows; i++)
@@ -471,18 +530,11 @@ gtk_list_view_add_rows (GtkListView              *self,
                         guint                     n_rows)
 {  
   ListRow *row;
-  guint i, n_total;
+  guint i;
 
   if (n_rows == 0)
     return;
 
-  n_total = self->model ? g_list_model_get_n_items (self->model) : 0;
-  if (self->anchor_pos >= position)
-    {
-      if (n_total != n_rows) /* the model was not empty before */
-        self->anchor_pos += n_rows;
-    }
-
   row = gtk_list_view_get_row (self, position, NULL);
 
   for (i = 0; i < n_rows; i++)
@@ -541,6 +593,15 @@ gtk_list_view_model_items_changed_cb (GListModel  *model,
   if (removed != added)
     gtk_list_view_update_rows (self, position + added);
 
+  if (gtk_list_item_manager_change_contains (change, self->anchor))
+    {
+      guint anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+      
+      /* removed cannot be NULL or the anchor wouldn't have been removed */
+      anchor_pos = position + (anchor_pos - position) * added / removed;
+      gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align);
+    }
+
   gtk_list_item_manager_end_change (self->item_manager, change);
 }
 
@@ -558,35 +619,6 @@ gtk_list_view_clear_model (GtkListView *self)
   g_clear_object (&self->model);
 }
 
-static void
-gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
-                                           GtkListView   *self)
-{
-  if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL])
-    {
-      ListRow *row;
-      guint pos;
-      int dy;
-
-      row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy);
-      if (row)
-        pos = list_row_get_position (self, row);
-      else
-        pos = 0;
-
-      if (pos != self->anchor_pos || dy != self->anchor_dy)
-        {
-          self->anchor_pos = pos;
-          self->anchor_dy = dy;
-          gtk_widget_queue_allocate (GTK_WIDGET (self));
-        }
-    }
-  else
-    { 
-      gtk_widget_queue_allocate (GTK_WIDGET (self));
-    }
-}
-
 static void
 gtk_list_view_clear_adjustment (GtkListView    *self,
                                 GtkOrientation  orientation)
@@ -866,6 +898,7 @@ gtk_list_view_set_model (GtkListView *self,
                         self);
 
       gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model));
+      gtk_list_view_set_anchor (self, 0, 0);
     }
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
@@ -879,13 +912,15 @@ gtk_list_view_set_functions (GtkListView          *self,
                              GDestroyNotify        user_destroy)
 {
   GtkListItemFactory *factory;
-  guint n_items;
+  guint n_items, anchor;
+  double anchor_align;
 
   g_return_if_fail (GTK_IS_LIST_VIEW (self));
   g_return_if_fail (setup_func || bind_func);
   g_return_if_fail (user_data != NULL || user_destroy == NULL);
 
   n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
+  anchor = gtk_list_view_get_anchor (self, &anchor_align);
   gtk_list_view_remove_rows (self, NULL, 0, n_items);
 
   factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy);
@@ -893,5 +928,6 @@ gtk_list_view_set_functions (GtkListView          *self,
   g_object_unref (factory);
 
   gtk_list_view_add_rows (self, NULL, 0, n_items);
+  gtk_list_view_set_anchor (self, anchor, anchor_align);
 }